package com.rkc.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rkc.component.PayFactory;
import com.rkc.config.RabbitMQConfiguration;
import com.rkc.constant.CacheKey;
import com.rkc.constant.TimeConstant;
import com.rkc.enums.*;
import com.rkc.exception.BizException;
import com.rkc.feign.CouponRecordFeignService;
import com.rkc.feign.ProductCartFeignService;
import com.rkc.feign.ProductFeignService;
import com.rkc.feign.UserAddressFeignService;
import com.rkc.interceptor.LoginInterceptor;
import com.rkc.mapper.ProductOrderItemMapper;
import com.rkc.mapper.ProductOrderMapper;
import com.rkc.model.LoginUser;
import com.rkc.model.OrderMessage;
import com.rkc.model.ProductOrderDO;
import com.rkc.model.ProductOrderItemDO;
import com.rkc.request.*;
import com.rkc.service.ProductOrderService;
import com.rkc.utils.CommonUtil;
import com.rkc.utils.JsonData;
import com.rkc.vo.CouponRecordVO;
import com.rkc.vo.OrderItemVO;
import com.rkc.vo.PayInfoVO;
import com.rkc.vo.ProductOrderAddressVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 冉堃赤
 * @since 2021-10-30
 */
@Slf4j
@Service
public class ProductOrderServiceImpl implements ProductOrderService {

    @Resource
    private ProductOrderMapper productOrderMapper;

    @Resource
    private ProductOrderItemMapper productOrderItemMapper;

    @Autowired
    private UserAddressFeignService userAddressFeignService;

    @Autowired
    private ProductCartFeignService productCartFeignService;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private CouponRecordFeignService couponRecordFeignService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfiguration rabbitMQConfiguration;

    @Autowired
    private PayFactory payFactory;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * - 防重提交
     * - 用户微服务-确认收货地址
     * - 商品微服务-获取最新购物项和价格
     * - 订单验价
     *   - 优惠券微服务-获取优惠券
     *   - 验证价格
     * - 锁定优惠券
     * - 锁定商品库存
     * - 创建订单对象
     * - 创建子订单对象
     * - 发送延迟消息-用于自动关单
     * - 创建支付信息-对接三方支付
     * @param confirmOrderRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData confirmOrder(ConfirmOrderRequest confirmOrderRequest) {
        LoginUser loginUser = LoginInterceptor.THREAD_LOCAL.get();
        //token校验防重提交
        String orderToken = confirmOrderRequest.getToken();
        if (StringUtils.isBlank(orderToken)) {
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_NOT_EXIST);
        }
        //保证原子操作，校验令牌再删除令牌
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        Long res = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getId())), orderToken);
        if (res == 0) {
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
        }

        String orderOutTradeNo = CommonUtil.getStringNumRandom(32);
        ProductOrderAddressVO addressVO = getUserAddress(confirmOrderRequest.getAddressId());
        log.info("收货地址信息：{}", addressVO);
        //用户购物车的所有商品，获得商品的最新价格
        List<Long> productIdList = confirmOrderRequest.getProductIdList();
        //List转成Set进行查找，加快查找速度
        HashSet<Long> productIdSet = new HashSet<>(productIdList);
        JsonData cartItemResult = productCartFeignService.confirmOrderCartItem(productIdSet);
        List<OrderItemVO> orderItemVOList = cartItemResult.getData(new TypeReference<List<OrderItemVO>>() {
        });
        log.info("获取的商品：{}", orderItemVOList);
        if (orderItemVOList == null) {
            log.warn("购物车商品不存在");
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_CART_ITEM_NOT_EXIST);
        }
        //验证价格，商品总价减去优惠券抵扣，验价失败则抛出异常，交易无法进行
        checkPrice(orderItemVOList, confirmOrderRequest);
        //锁定优惠券
        lockCouponRecords(confirmOrderRequest, orderOutTradeNo);
        //锁定库存
        lockProductStocks(orderItemVOList, orderOutTradeNo);
        //创建订单和订单项
        ProductOrderDO productOrderDO = saveProductOrder(confirmOrderRequest, loginUser, orderOutTradeNo, addressVO);
        saveProductOrderItems(orderOutTradeNo, productOrderDO.getId(), orderItemVOList);
        //发送延迟消息，用于自动关单
        OrderMessage orderMessage = new OrderMessage();
        orderMessage.setOutTradeNo(orderOutTradeNo);
        rabbitTemplate.convertAndSend(rabbitMQConfiguration.getEventExchange(), rabbitMQConfiguration.getOrderCloseDelayRoutingKey(), orderMessage);
        //创建支付
        PayInfoVO payInfoVO = new PayInfoVO(orderOutTradeNo, productOrderDO.getPayAmount(), confirmOrderRequest.getPayType(),
                confirmOrderRequest.getClientType(), orderItemVOList.get(0).getProductTitle(), "description", TimeConstant.ORDER_PAY_TIMEOUT_MILLS);
        //payResult就是html的返回表单
        String payResult = payFactory.pay(payInfoVO);
        if (!StringUtils.isBlank(payResult)) {
            log.info("创建支付订单成功：payResult={}, payInfo={}", payResult, payInfoVO);
            return JsonData.buildSuccess(payResult);
        }
        log.error("创建支付订单失败：payResult={}, payInfo={}", payResult, payInfoVO);
        return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL);
    }

    /**
     * 创建订单项
     * @param orderOutTradeNo
     * @param productOrderId
     * @param orderItemVOList
     */
    private void saveProductOrderItems(String orderOutTradeNo, Long productOrderId, List<OrderItemVO> orderItemVOList) {
        List<ProductOrderItemDO> itemDOList = orderItemVOList.stream().map(obj -> {
            ProductOrderItemDO productOrderItemDO = new ProductOrderItemDO();
            productOrderItemDO.setBuyNum(obj.getBuyNum());
            productOrderItemDO.setCreateTime(new Date());
            productOrderItemDO.setProductId(obj.getProductId());
            productOrderItemDO.setOutTradeNo(orderOutTradeNo);
            productOrderItemDO.setProductImg(obj.getProductImg());
            productOrderItemDO.setProductName(obj.getProductTitle());
            //单价、总价和所属的订单
            productOrderItemDO.setAmount(obj.getAmount());
            productOrderItemDO.setTotalAmount(obj.getTotalAmount());
            productOrderItemDO.setProductOrderId(productOrderId);
            return productOrderItemDO;
        }).collect(Collectors.toList());
        productOrderItemMapper.insertBatch(itemDOList);
    }

    /**
     * 创建订单
     * @param orderRequest
     * @param loginUser
     * @param orderOutTradeNo
     * @param addressVO
     * @return
     */
    private ProductOrderDO saveProductOrder(ConfirmOrderRequest orderRequest, LoginUser loginUser, String orderOutTradeNo,
                                            ProductOrderAddressVO addressVO) {
        ProductOrderDO productOrderDO = new ProductOrderDO();
        productOrderDO.setUserId(loginUser.getId());
        productOrderDO.setHeadImg(loginUser.getHeadImg());
        productOrderDO.setNickname(loginUser.getName());
        productOrderDO.setOutTradeNo(orderOutTradeNo);
        productOrderDO.setCreateTime(new Date());
        productOrderDO.setDel(0);
        productOrderDO.setOrderType(ProductOrderTypeEnum.DAILY.name());
        productOrderDO.setPayAmount(orderRequest.getRealPayAmount());
        //未使用优惠券的总价
        productOrderDO.setTotalAmount(orderRequest.getTotalAmount());
        productOrderDO.setState(ProductOrderStateEnum.NEW.name());
        //支付类型
        productOrderDO.setPayType(ProductOrderPayTypeEnum.valueOf(orderRequest.getPayType()).name());
        productOrderDO.setReceiverAddress(JSON.toJSONString(addressVO));
        productOrderMapper.insert(productOrderDO);
        return productOrderDO;
    }

    /**
     * 锁定商品库存
     * @param orderItemVOList
     * @param orderOutTradeNo
     */
    private void lockProductStocks(List<OrderItemVO> orderItemVOList, String orderOutTradeNo) {
        List<OrderItemRequest> requests = orderItemVOList.stream().map(obj -> {
            OrderItemRequest orderItemRequest = new OrderItemRequest();
            orderItemRequest.setBuyNum(obj.getBuyNum());
            orderItemRequest.setProductId(obj.getProductId());
            return orderItemRequest;
        }).collect(Collectors.toList());
        LockProductRequest lockProductRequest = new LockProductRequest();
        lockProductRequest.setOrderOutTradeNo(orderOutTradeNo);
        lockProductRequest.setOrderItemList(requests);
        JsonData jsonData = productFeignService.lockProductStock(lockProductRequest);
        if (jsonData.getCode() != 0) {
            log.error("锁定商品库存失败：{}", lockProductRequest);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_LOCK_PRODUCT_FAIL);
        }
    }

    /**
     * 锁定优惠券
     * @param confirmOrderRequest
     * @param orderOutTradeNo
     */
    private void lockCouponRecords(ConfirmOrderRequest confirmOrderRequest, String orderOutTradeNo) {
        List<Long> lockCouponRecordIds = new ArrayList<>();
        if (confirmOrderRequest.getCouponRecordId() > 0) {
            lockCouponRecordIds.add(confirmOrderRequest.getCouponRecordId());
            LockCouponRecordRequest lockCouponRecordRequest = new LockCouponRecordRequest();
            lockCouponRecordRequest.setOrderOutTradeNo(orderOutTradeNo);
            lockCouponRecordRequest.setLockCouponRecordIds(lockCouponRecordIds);
            //发起锁定优惠券请求
            JsonData jsonData = couponRecordFeignService.lockCouponRecords(lockCouponRecordRequest);
            if (jsonData.getCode() != 0) {
                throw new BizException(BizCodeEnum.COUPON_RECORD_LOCK_FAIL);
            }
        }
    }

    /**
     * 验证价格
     * 1）统计全部商品的价格
     * 2）获取优惠券（判断是否满足优惠券条件），总价减去优惠券的价格就是最终价格
     * @param orderItemVOList
     * @param confirmOrderRequest
     */
    private void checkPrice(List<OrderItemVO> orderItemVOList, ConfirmOrderRequest confirmOrderRequest) {
        //统计商品总价
        BigDecimal realPayAmount = new BigDecimal("0");
        for (OrderItemVO orderItemVO : orderItemVOList) {
            BigDecimal itemRealPayAmount = orderItemVO.getTotalAmount();
            realPayAmount = realPayAmount.add(itemRealPayAmount);
        }
        //获取优惠券，判断是否可以使用
        CouponRecordVO couponRecordVO = getCartCouponRecord(confirmOrderRequest.getCouponRecordId());
        //计算购物车价格是否满足优惠券满减条件
        if (couponRecordVO != null) {
            if (realPayAmount.compareTo(couponRecordVO.getConditionPrice()) < 0) {
                //不满足满减
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL);
            }
            //抵扣金额大于购买需要支付的金额，直接改为0元
            realPayAmount = couponRecordVO.getPrice().compareTo(realPayAmount) > 0 ? realPayAmount = BigDecimal.ZERO :
                    realPayAmount.subtract(couponRecordVO.getPrice());
        }
        //与前端传递过来的价格对比，进行验价
        if (realPayAmount.compareTo(confirmOrderRequest.getRealPayAmount()) != 0) {
            log.error("订单验价失败：{}", confirmOrderRequest);
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_PRICE_FAIL);
        }
    }

    /**
     * 获取优惠券的信息
     * @param couponRecordId
     * @return
     */
    private CouponRecordVO getCartCouponRecord(Long couponRecordId) {
        if (couponRecordId == null || couponRecordId < 0) {
            return null;
        }
        JsonData couponRecordResult = couponRecordFeignService.findUserCouponRecordById(couponRecordId);
        if (couponRecordResult.getCode() != 0) {
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_COUPON_FAIL);
        }
        if (couponRecordResult.getCode() == 0) {
            CouponRecordVO couponRecordVO = couponRecordResult.getData(new TypeReference<CouponRecordVO>() {
            });
            //判断是否满足使用条件
            if (!couponAvailable(couponRecordVO)) {
                log.error("优惠券使用失败");
                throw new BizException(BizCodeEnum.COUPON_UNAVAILABLE);
            }
            return couponRecordVO;
        }
        return null;
    }

    /**
     * 判断优惠券是否可用
     * @param couponRecordVO
     * @return
     */
    private boolean couponAvailable(CouponRecordVO couponRecordVO) {
        if (couponRecordVO.getUseState().equalsIgnoreCase(CouponStateEnum.NEW.name())) {
            long currentTimestamp = CommonUtil.getCurrentTimestamp();
            long end = couponRecordVO.getEndTime().getTime(), start = couponRecordVO.getStartTime().getTime();
            return currentTimestamp >= start && currentTimestamp <= end;
        }
        return false;
    }

    /**
     * 从购物车中删除购买的物品，在所有流程都正确执行完后被调用
     * @param productIdSet
     */
    private void deleteProductFromCart(HashSet<Long> productIdSet) {
        JsonData deletionResult = productCartFeignService.deleteItemList(productIdSet);
        if (deletionResult.getCode() != 0) {
            //也可以使用MQ进行异步删除，在消息消费者方重试删除
            log.warn("购物车删除项失败：{}", deletionResult);
            throw new BizException(BizCodeEnum.CART_DELETE_ITEMS_FAIL);
        }
    }

    /**
     * 获取收货地址详情
     * @param addressId
     * @return
     */
    private ProductOrderAddressVO getUserAddress(Long addressId) {
        JsonData result = userAddressFeignService.detail(addressId);
        if (result.getCode() != 0) {
            log.error("获取地址详情失败：{}", result);
            throw new BizException(BizCodeEnum.ADDRESS_NO_EXITS);
        }
        ProductOrderAddressVO addressVO = result.getData(new TypeReference<ProductOrderAddressVO>() {
        });
        log.info("收货地址信息：{}", addressVO);

        return addressVO;
    }

    @Override
    public String queryProductOrderState(String outTradeNo) {
        ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no",
                outTradeNo));
        if (productOrderDO == null) {
            return "";
        }
        return productOrderDO.getState();
    }

    /**
     * 超时自动关单，由监听器间接调用
     * @param orderMessage
     * @return
     */
    @Override
    public boolean closeProductOrder(OrderMessage orderMessage) {
        ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no",
                orderMessage.getOutTradeNo()));
        if (productOrderDO == null) {
            //订单不存在
            log.warn("直接确认消息，订单不存在：{}", orderMessage);
            return true;
        }
        //存在订单则查询订单状态，如果已经支付，也不需要处理
        if (productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.PAY.name())) {
            log.info("订单已支付，请确认消息：{}", orderMessage);
            return true;
        }
        //向第三方支付查询订单支付状态
        PayInfoVO payInfoVO = new PayInfoVO();
        payInfoVO.setPayType(productOrderDO.getPayType());
        payInfoVO.setOutTradeNo(orderMessage.getOutTradeNo());
        String payResult = payFactory.queryPayState(payInfoVO);
        //查询结果为空，则支付未成功，取消订单
        if (StringUtils.isBlank(payResult)) {
            productOrderMapper.updateOrderPayState(orderMessage.getOutTradeNo(), ProductOrderStateEnum.CANCEL.name(), ProductOrderStateEnum.NEW.name());
            log.info("未支付成功，订单取消成功：{}", orderMessage);
            return true;
        }
        //支付成功，主动把订单状态改成已支付，如果代码运行到这里很有可能是由于支付通道的回调出现了问题
        productOrderMapper.updateOrderPayState(orderMessage.getOutTradeNo(), ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
        log.warn("支付的回调可能出现了问题，建议排查：{}", orderMessage);
        return true;
    }

    /**
     * 支付通知结果更新订单状态
     * @param payType
     * @param paramsMap
     * @return
     */
    @Override
    public JsonData handleOrderCallback(ProductOrderPayTypeEnum payType, Map<String, String> paramsMap) {
        if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.ALIPAY.name())) {
            //支付宝支付。获取商户订单号，交易状态
            String outTradeNo = paramsMap.get("out_trade_no"), tradeStatus = paramsMap.get("trade_status");
            //不支持退款
            if ("TRADE_SUCCESS".equalsIgnoreCase(tradeStatus) || "TRADE_FINISHED".equalsIgnoreCase(tradeStatus)) {
                //更新订单状态
                productOrderMapper.updateOrderPayState(outTradeNo, ProductOrderStateEnum.PAY.name(), ProductOrderStateEnum.NEW.name());
                return JsonData.buildSuccess();
            }
        } else if (payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.WECHAT.name())) {
            //TODO 微信支付的处理
        }
        return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public JsonData repay(RepayOrderRequest repayOrderRequest) {
        LoginUser loginUser = LoginInterceptor.THREAD_LOCAL.get();
        ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().eq("out_trade_no", repayOrderRequest.getOutTradeNo())
                .eq("user_id", loginUser.getId()));
        if (productOrderDO == null) {
            return JsonData.buildResult(BizCodeEnum.PAY_ORDER_NOT_EXIST);
        }
        //不是NEW状态的订单不予处理
        if (!productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())) {
            return JsonData.buildResult(BizCodeEnum.PAY_ORDER_STATE_ERROR);
        }
        log.info("订单状态：{}", productOrderDO);
        //订单创建到现在的存活时间
        long orderLiveTime = CommonUtil.getCurrentTimestamp() - productOrderDO.getCreateTime().getTime();
        //创建订单是临界点，所以多提前一点就不让支付；大于订单配置的超时时间，则失效
        orderLiveTime += 70 * 1000;
        if (orderLiveTime > TimeConstant.ORDER_PAY_TIMEOUT_MILLS) {
            return JsonData.buildResult(BizCodeEnum.PAY_ORDER_PAY_TIMEOUT);
        }
        //剩余有效时间=总时间-存活的时间
        long timeout = TimeConstant.ORDER_PAY_TIMEOUT_MILLS - orderLiveTime;
        PayInfoVO payInfoVO = new PayInfoVO(productOrderDO.getOutTradeNo(), productOrderDO.getPayAmount(), repayOrderRequest.getPayType(),
                repayOrderRequest.getClientType(), productOrderDO.getOutTradeNo(), "description", timeout);
        log.info("payInfo={}", payInfoVO);
        String payResult = payFactory.pay(payInfoVO);
        if (!StringUtils.isBlank(payResult)) {
            log.info("创建二次支付订单成功：payResult={}, payInfo={}", payResult, payInfoVO);
            return JsonData.buildSuccess(payResult);
        }
        log.error("创建二次支付订单失败：payResult={}, payInfo={}", payResult, payInfoVO);
        return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL);
    }
}
